package com.yf.ability.face.provides.aliyun.service.impl;

import com.alibaba.fastjson.JSON;
import com.aliyun.com.viapi.FileUtils;
import com.aliyun.facebody20191230.Client;
import com.aliyun.facebody20191230.models.*;
import com.aliyun.teaopenapi.models.Config;
import com.aliyuncs.exceptions.ClientException;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.yf.ability.config.dto.CfgPropDTO;
import com.yf.ability.config.enums.ConfigType;
import com.yf.ability.config.enums.ProviderType;
import com.yf.ability.config.service.CfgPropService;
import com.yf.ability.face.enums.FaceImageType;
import com.yf.ability.face.provides.aliyun.config.AliyunFaceConfig;
import com.yf.ability.face.service.ThirdFaceService;
import com.yf.base.api.exception.ServiceException;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.List;

/**
 * 阿里云人脸识别业务实现类
 * 阿里云的人脸处理较为复杂，添加人脸和识别只支持上海节点的oss地址，需要经过临时文件转换才可以，好在其提供了专门的临时文件上传，详见：
 * https://help.aliyun.com/document_detail/155645.htm#section-a6s-zui-3uf
 * @author van
 */
@Log4j2
@Service("aliyunFaceService")
public class AliyunFaceServiceImpl implements ThirdFaceService {


    // 区域，阿里云固定地址
    public static final String REGION = "facebody.cn-shanghai.aliyuncs.com";


    @Autowired
    private CfgPropService cfgPropService;


    @Override
    public void addFace(String imageType, String image, String userId) {


        try {
            AliyunFaceConfig cfg = this.getConfig();
            Client client = this.getClient();

            // 检查人脸数据库
            this.checkFaceDb(client, cfg.getDbName());

            // 先添加样本
            this.addSample(cfg, client, userId);

            // 再添加人脸
            String ossUrl = this.uploadTemp(cfg, FaceImageType.URL, image);
            AddFaceRequest request = new AddFaceRequest()
                    .setDbName(cfg.getDbName())
                    // 只能传到上海
                    .setImageUrl(ossUrl)
                    .setEntityId(userId);
            AddFaceResponse response = client.addFace(request);

            log.error("ali添加人脸数据：", JSON.toJSONString(response));

        } catch (Exception e) {
            log.error(e);
            throw new ServiceException("人脸添加失败："+e.getMessage());
        }

    }

    @Override
    public void deleteFace(String userId) {

        try {
            AliyunFaceConfig cfg = this.getConfig();
            // 删除人脸请求
            DeleteFaceEntityRequest entityRequest = new DeleteFaceEntityRequest();
            entityRequest.setEntityId(userId);
            entityRequest.setDbName(cfg.getDbName());

            Client client = this.getClient();
            DeleteFaceEntityResponse entityResponse = client.deleteFaceEntity(entityRequest);
            log.error("ali删除样本结果：" , JSON.toJSONString(entityResponse));

        } catch (Exception e) {
            log.error(e);
            // 忽略删除错误
        }
    }

    @Override
    public void updateFace(String imageType, String image, String userId) {
        // 先删除
        this.deleteFace(userId);
        // 再添加
        this.addFace(imageType, image, userId);
    }

    @Override
    public String searchFace(String imageType, String image) {

        try {
            AliyunFaceConfig cfg = this.getConfig();
            SearchFaceRequest request = new SearchFaceRequest();
            request.setDbName(cfg.getDbName());
            request.setLimit(1);
            String ossUrl = this.uploadTemp(cfg,  FaceImageType.BASE64, image);
            request.setImageUrl(ossUrl);
            log.error("ali搜索地址：", ossUrl);
            SearchFaceResponse response = this.getClient().searchFace(request);
            // 搜索到的结果列表
            List<SearchFaceResponseBody.SearchFaceResponseBodyDataMatchList> list = response.getBody().getData().getMatchList();
            log.error("ali人脸搜索结果：", JSON.toJSONString(response));


            // 如果为空表示找不到
            if(CollectionUtils.isEmpty(list) || CollectionUtils.isEmpty(list.get(0).getFaceItems())){
                throw new ServiceException("识别失败，未找到匹配的人脸！");
            }

            // 返回的结果，因为是单张人脸，所以取第一个
            List<SearchFaceResponseBody.SearchFaceResponseBodyDataMatchListFaceItems> results = list.get(0).getFaceItems();

            if(CollectionUtils.isEmpty(results)){
                throw new ServiceException("识别失败，未找到匹配的人脸！");
            }

            String userId = null;
            Float score = -1f;

            for(SearchFaceResponseBody.SearchFaceResponseBodyDataMatchListFaceItems item: results){
                // 大于0.8则认为是本人，图片质量低就改低一些
                if(item.getScore() > 0.7f && item.getScore()> score){
                    userId = item.getEntityId();
                    score = item.getScore();
                }
            }

            if(StringUtils.isBlank(userId)){
                throw new ServiceException("识别失败，未找到匹配的人脸！");
            }

            return userId;

        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }

    @Override
    public void mergeFace(boolean isAdd, String oldFace, String newFace, String userId) {

        // 添加模式
        if(isAdd){
            if(!StringUtils.isBlank(newFace)){
                // 直接添加
                this.addFace(FaceImageType.URL, newFace, userId);
            }
        }else{

            // 清空，删除人脸
            if(StringUtils.isBlank(newFace) && !StringUtils.isBlank(oldFace)){
                this.deleteFace(userId);
                return;
            }

            // 添加过，更新
            if(!StringUtils.isBlank(newFace) && !StringUtils.isBlank(oldFace)){
                this.updateFace(FaceImageType.URL, newFace, userId);
                return;
            }

            // 未添加过，增加
            if(!StringUtils.isBlank(newFace) && StringUtils.isBlank(oldFace)){
                this.addFace(FaceImageType.URL, newFace, userId);
                return;
            }
        }

    }

    /**
     * 添加样本数据
     * @param cfg
     * @param client
     * @param userId
     * @throws Exception
     */
    private void addSample(AliyunFaceConfig cfg, Client client, String userId) throws Exception{


        GetFaceEntityRequest getRequest = new GetFaceEntityRequest();
        getRequest.setEntityId(userId);
        getRequest.setDbName(cfg.getDbName());
        GetFaceEntityResponse getResponse = client.getFaceEntity(getRequest);

        // 样本存在则不添加
        if(getResponse.getBody().getData()!=null
                && !StringUtils.isBlank(getResponse.getBody().getData().getEntityId())){
            return;
        }


        AddFaceEntityRequest entityRequest = new AddFaceEntityRequest();
        entityRequest.setEntityId(userId);
        entityRequest.setDbName(cfg.getDbName());

        AddFaceEntityResponse entityResponse = client.addFaceEntity(entityRequest);
        log.error("ali添加人脸样本：", JSON.toJSONString(entityResponse));
    }


    /**
     * 获取配置文件
     * @return
     */
    private AliyunFaceConfig getConfig(){
        CfgPropDTO dto = cfgPropService.detail(ConfigType.FACE, ProviderType.ALIYUN);
        AliyunFaceConfig cfg = JSON.parseObject(dto.getData(), AliyunFaceConfig.class);
        return cfg;
    }

    /**
     * 获得请求接口
     * @return
     * @throws Exception
     */
    private Client getClient() throws Exception {

        AliyunFaceConfig cfg = this.getConfig();

        Config config = new Config()
                .setAccessKeyId(cfg.getAccessKeyId())
                .setAccessKeySecret(cfg.getAccessKeySecret());
        // 访问的域名
        config.endpoint = REGION;
        return new Client(config);
    }

    /**
     * 校验和创建人脸数据库
     * @return
     */
    private void checkFaceDb(Client client, String dbName){

        // 检测数据库是否存在
        try {

            ListFaceDbsResponse listResponse = client.listFaceDbs();
            List<ListFaceDbsResponseBody.ListFaceDbsResponseBodyDataDbList> list = listResponse.getBody().getData().getDbList();
            for(ListFaceDbsResponseBody.ListFaceDbsResponseBodyDataDbList item: list){
                if(dbName.equalsIgnoreCase(item.getName())){
                    return;
                }
            }

            CreateFaceDbRequest dbRequest = new CreateFaceDbRequest();
            dbRequest.setName(dbName);
            CreateFaceDbResponse dbResponse = client.createFaceDb(dbRequest);
            log.error("ali人脸库创建：", JSON.toJSONString(dbResponse));

        } catch (Exception e) {
            log.error(e);
            throw new ServiceException("人脸数据库创建失败："+e.getMessage());
        }

    }

    /**
     * 上传文件到OSS临时库中
     * @param cfg
     * @param image
     * @return
     */
    private String uploadTemp(AliyunFaceConfig cfg, String fileType, String image) throws ClientException, IOException {
        // 传到上海临时OSS中获得地址
        FileUtils fileUtils = FileUtils.getInstance(cfg.getAccessKeyId(), cfg.getAccessKeySecret());

        // base64的上传
        if(FaceImageType.BASE64.equals(fileType)){
            byte[] bytes = Base64.getDecoder().decode(image);
            ByteArrayInputStream is = new ByteArrayInputStream(bytes);
            return fileUtils.upload(IdWorker.getIdStr()+".jpg", is);
        }

        // url格式上传
        String ossUrl = fileUtils.upload(image);
        return ossUrl;
    }
}
